﻿// 2022/4/1: 首个版本
// 2022/4/29: 增加PluginPackVersionRef.native
// 2022/6/14: 增加PluginPackContext.Depends
// 2022/8/17: DriverFileNames支持多平台
// 2022/11/21: EnderInfo通过静态属性设置
// 2022/11/24: 增加ReleaseDate字段
// 2023/4/17: 修正IsManualPartSame
// 2023/9/13: Validate增加chinese参数
// 2023/10/9: 添加英文注释
// 2024/4/16: 支持API3接口
// 2024/6/20: 中文语言代号改为zh
// 2024/6/21: 使用AgencyLocal
// 2024/6/24: 增加agency字段
// 2024/6/26: 使用Language枚举
// 2024/8/26: 新增PluginPackContext.LoadText。Validate增加checkFileExists参数

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;
using ASEva;
using SO = System.Reflection.ObfuscationAttribute;

namespace CommonSource
{
    [SO]
    enum PluginPackVersionRef
    {
        app = 0,
        bus,
        video,
        proc,
        dev,
        file,
        native,
    }

    class ComponentInfo
    {
        public String[] Depends { get; set; }
        public Dictionary<Language, String> Title { get; set; }
    }

    partial class PluginPackContext
    {
        // Ending information of the file / 保存文件的结尾信息
        public static String EnderInfo { get; set; }

        // File path / 文件路径
        public String TxtFilePath { get; set; }

        // User editable infos / 用户编辑信息
        public String ID { get; set; }
        public Dictionary<Language, String> Name { get; set; }
        public Dictionary<Language, String> Brief { get; set; }
        public PluginPackVersionRef VersionRef { get; set; } // Version source / 版本源
        public String AppEntryFile { get; set; } // C# entry file (relative path) / C#入口文件（相对路径）
        public String[] LibFiles { get; set; } // C#/C++ library files (relative path) / C#/C++库文件（相对路径）
        public String[] ExeFiles { get; set; } // Executable files / 可执行文件（相对路径）
        public Dictionary<String, String[]> DriverFileNames { get; set; } // Driver, environment installer, script zip package / 驱动，环境安装程序，脚本zip包名

        // Auto generated infos / 自动生成信息
        public Version PlainVersion { get; set; } // Version / 版本号
        public DateTime? ReleaseDate { get; set; } // Release date / 发布日期
        public String PlatformCode { get; set; } // {OS code}.{UI framework code} / {OS代号}.{UI框架代号}
        public String[] NativeTypes { get; set; } // C++ component's native type / 包含的C++组件类型
        public String[] Depends { get; set; } // C++ component dependencies / 插件自身的native依赖项
        public Dictionary<String, ComponentInfo> Components { get; set; } // ID and dependencies of components / 包含的组件ID及各组件的依赖项/标题等信息
        public Dictionary<String, Version> ApisUsed { get; set; } // Versions of used API / 实际使用的API版本
        public bool FreeAgency { get; set; } // Not using legacy Agency / 未使用旧Agency

        public bool Validate(out String error, bool? chinese = null, bool checkFileExists = true)
        {
            error = null;

            bool zh = false;
            if (chinese == null) zh = AgencyLocal.GetAppLanguage() == Language.Chinese;
            else zh = chinese.Value;

            if (checkFileExists)
            {
                if (String.IsNullOrEmpty(TxtFilePath) || !File.Exists(TxtFilePath))
                {
                    error = zh ? "无效的文件路径。" : "Invalid file path.";
                    return false;
                }
            }

            if (String.IsNullOrEmpty(ID))
            {
                error = zh ? "ID为空。" : "Empty ID.";
                return false;
            }

            if (!Name.ContainsKey(Language.English) || String.IsNullOrEmpty(Name[Language.English]))
            {
                error = zh ? "英文名称为空。" : "Empty English name.";
                return false;
            }

            if (!Name.ContainsKey(Language.Chinese) || String.IsNullOrEmpty(Name[Language.Chinese]))
            {
                error = zh ? "中文名称为空。" : "Empty Chinese name.";
                return false;
            }

            if (!Brief.ContainsKey(Language.English) || String.IsNullOrEmpty(Brief[Language.English]))
            {
                error = zh ? "英文简介为空。" : "Empty English brief.";
                return false;
            }

            if (!Brief.ContainsKey(Language.Chinese) || String.IsNullOrEmpty(Brief[Language.Chinese]))
            {
                error = zh ? "中文简介为空。" : "Empty Chinese brief.";
                return false;
            }

            String dir = checkFileExists ? (Path.GetDirectoryName(TxtFilePath) + Path.DirectorySeparatorChar) : null;
            
            if (!String.IsNullOrEmpty(AppEntryFile))
            {
                if (!AppEntryFile.ToLower().EndsWith(".dll"))
                {
                    error = zh ? "C#入口文件后缀非dll。" : "C# entry file suffix is not dll.";
                    return false;
                }
                if (checkFileExists && !File.Exists(dir + AppEntryFile))
                {
                    error = zh ? "C#入口文件不存在。" : "C# entry file doesn't exist.";
                    return false;
                }
            }

            if (checkFileExists && LibFiles != null)
            {
                foreach (var f in LibFiles)
                {
                    if (!File.Exists(dir + f))
                    {
                        error = (zh ? "库文件不存在：" : "Library file doesn't exist: ") + f;
                        return false;
                    }
                }
            }
            if (checkFileExists && ExeFiles != null)
            {
                foreach (var f in ExeFiles)
                {
                    if (!File.Exists(dir + f))
                    {
                        error = (zh ? "可执行文件不存在：" : "Executable file doesn't exist: ") + f;
                        return false;
                    }
                }
            }

            return true;
        }

        public void Save()
        {
            Export(TxtFilePath);
        }

        public void Export(String targetPath)
        {
            if (String.IsNullOrEmpty(targetPath)) return;

            StreamWriter writer = null;
            try
            {
                writer = new StreamWriter(targetPath, false, Encoding.UTF8);
                if (!String.IsNullOrEmpty(ID)) writer.WriteLine("id=" + ID);
                foreach (var pair in Name)
                {
                    if (pair.Key == Language.English) writer.WriteLine("name:en=" + pair.Value);
                    else if (pair.Key == Language.Chinese) writer.WriteLine("name:zh=" + pair.Value);
                }
                writer.WriteLine("version=" + VersionRef.ToString());
                if (PlainVersion != null) writer.WriteLine("plainver=" + PlainVersion.ToString());
                if (ReleaseDate != null) writer.WriteLine("date=" + ReleaseDate.Value.ToString("yyyy-MM-dd"));
                if (!String.IsNullOrEmpty(PlatformCode)) writer.WriteLine("platform=" + PlatformCode);
                foreach (var pair in ApisUsed) writer.WriteLine("api:" + pair.Key + "=" + pair.Value.ToString());
                if (NativeTypes != null && NativeTypes.Length > 0) writer.WriteLine("type=" + String.Join(",", NativeTypes));
                writer.WriteLine("agency=" + (FreeAgency ? "free" : "legacy"));
                if (Depends != null && Depends.Length > 0) writer.WriteLine("depend=" + String.Join(",", Depends));
                if (Components != null && Components.Count > 0)
                {
                    var componentList = new List<String>();
                    foreach (var pair in Components)
                    {
                        if (pair.Value == null) continue;
                        componentList.Add(pair.Key);
                        if (pair.Value.Title != null)
                        {
                            foreach (var titlePair in pair.Value.Title)
                            {
                                if (titlePair.Key == Language.English) writer.WriteLine("title:en:" + pair.Key + "=" + titlePair.Value);
                                else if (titlePair.Key == Language.Chinese) writer.WriteLine("title:zh:" + pair.Key + "=" + titlePair.Value);
                            }
                        }
                        if (pair.Value.Depends != null && pair.Value.Depends.Length > 0)
                        {
                            writer.WriteLine("depend:" + pair.Key + "=" + String.Join(",", pair.Value.Depends));
                        }
                    }
                    writer.WriteLine("component=" + String.Join(",", componentList));
                }
                if (AppEntryFile != null && AppEntryFile.Length > 0) writer.WriteLine("app=" + AppEntryFile);
                if (LibFiles != null && LibFiles.Length > 0) writer.WriteLine("lib=" + String.Join(",", LibFiles));
                if (ExeFiles != null && ExeFiles.Length > 0) writer.WriteLine("exe=" + String.Join(",", ExeFiles));
                if (DriverFileNames != null)
                {
                    foreach (var pair in DriverFileNames)
                    {
                        var key = "driver" + (pair.Key.Length == 0 ? "" : (":" + pair.Key));
                        if (pair.Value != null && pair.Value.Length > 0) writer.WriteLine(key + "=" + String.Join(",", pair.Value));
                    }
                }
                foreach (var pair in Brief)
                {
                    if (pair.Key == Language.English) writer.WriteLine("brief:en=" + pair.Value);
                    else if (pair.Key == Language.Chinese) writer.WriteLine("brief:zh=" + pair.Value);
                }
                if (!String.IsNullOrEmpty(EnderInfo)) writer.WriteLine(EnderInfo);
                writer.Close();
            }
            catch (Exception)
            {
                if (writer != null) writer.Close();
            }
        }

        private bool isListSame(List<String> thisList, List<String> targetList)
        {
            thisList.Sort();
            targetList.Sort();
            return String.Join(',', thisList) == String.Join(',', targetList);
        }

        public bool IsManualPartSame(PluginPackContext target)
        {
            if (target == null) return false;

            String dummy;
            if (!Validate(out dummy) || !target.Validate(out dummy)) return false;

            if (ID != target.ID) return false;
            if (Name[Language.English] != target.Name[Language.English]) return false;
            if (Name[Language.Chinese] != target.Name[Language.Chinese]) return false;
            if (Brief[Language.English] != target.Brief[Language.English]) return false;
            if (Brief[Language.Chinese] != target.Brief[Language.Chinese]) return false;
            if (VersionRef != target.VersionRef) return false;

            var thisList = new List<String>();
            var targetList = new List<String>();

            if (AppEntryFile != null) thisList.Add(AppEntryFile);
            if (target.AppEntryFile != null) targetList.Add(target.AppEntryFile);
            if (!isListSame(thisList, targetList)) return false;

            thisList.Clear();
            targetList.Clear();

            if (LibFiles != null) thisList.AddRange(LibFiles);
            if (target.LibFiles != null) targetList.AddRange(target.LibFiles);
            if (!isListSame(thisList, targetList)) return false;

            thisList.Clear();
            targetList.Clear();

            if (ExeFiles != null) thisList.AddRange(ExeFiles);
            if (target.ExeFiles != null) targetList.AddRange(target.ExeFiles);
            if (!isListSame(thisList, targetList)) return false;

            thisList.Clear();
            targetList.Clear();

            if (DriverFileNames != null) thisList.AddRange(DriverFileNames.Keys);
            if (target.DriverFileNames != null) targetList.AddRange(target.DriverFileNames.Keys);
            if (!isListSame(thisList, targetList)) return false;

            if (DriverFileNames != null)
            {
                foreach (var key in DriverFileNames.Keys)
                {
                    thisList.Clear();
                    targetList.Clear();

                    if (DriverFileNames[key] != null) thisList.AddRange(DriverFileNames[key]);
                    if (target.DriverFileNames[key] != null) targetList.AddRange(target.DriverFileNames[key]);
                    if (!isListSame(thisList, targetList)) return false;
                }
            }

            return true;
        }

        private PluginPackContext()
        { }

        public static PluginPackContext Create(String txtFile)
        {
            var cxt = new PluginPackContext();
            cxt.TxtFilePath = txtFile;
            cxt.ID = "";
            cxt.Name = new Dictionary<Language, string>();
            cxt.PlatformCode = "";
            cxt.ApisUsed = new Dictionary<string, Version>();
            cxt.Brief = new Dictionary<Language, string>();
            cxt.Save();
            return cxt;
        }

        public static PluginPackContext LoadText(String txt)
        {
            if (String.IsNullOrEmpty(txt)) return null;

            var cxt = new PluginPackContext();
            cxt.TxtFilePath = null;
            cxt.ID = "";
            cxt.Name = new Dictionary<Language, string>();
            cxt.PlatformCode = "";
            cxt.ApisUsed = new Dictionary<string, Version>();
            cxt.Brief = new Dictionary<Language, string>();
            cxt.DriverFileNames = new Dictionary<string, string[]>();

            try
            {
                foreach (var rowText in txt.Replace("\r", "").Split('\n'))
                {
                    handleLine(rowText, cxt);
                }
            }
            catch (Exception) {}

            return cxt;
        }

        public static PluginPackContext Load(String txtFile, bool createIfNotExist)
        {
            if (String.IsNullOrEmpty(txtFile)) return null;
            if (!File.Exists(txtFile))
            {
                if (createIfNotExist) return Create(txtFile);
                else return null;
            }

            var cxt = new PluginPackContext();
            cxt.TxtFilePath = txtFile;
            cxt.ID = "";
            cxt.Name = new Dictionary<Language, string>();
            cxt.PlatformCode = "";
            cxt.ApisUsed = new Dictionary<string, Version>();
            cxt.Brief = new Dictionary<Language, string>();
            cxt.DriverFileNames = new Dictionary<string, string[]>();

            StreamReader reader = null;
            try
            {
                reader = new StreamReader(txtFile);

                while (true)
                {
                    var rowText = reader.ReadLine();
                    if (rowText == null) break;
                    handleLine(rowText, cxt);
                }
            }
            catch (Exception) {}
            if (reader != null) reader.Close();

            return cxt;
        }

        private static void handleLine(String rowText, PluginPackContext cxt)
        {
            if (rowText.StartsWith("id=") && rowText.Length > 3)
            {
                cxt.ID = rowText.Substring(3);
            }
            if (rowText.StartsWith("name:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 5 && rowText.Length > firstIndexOfEqual + 1)
                {
                    var langID = rowText.Substring(5, firstIndexOfEqual - 5);
                    Language language = Language.Invalid;
                    if (langID == "en") language = Language.English;
                    else if (langID == "zh" || langID == "ch") language = Language.Chinese;
                    if (language != Language.Invalid) cxt.Name[language] = rowText.Substring(firstIndexOfEqual + 1);
                }
            }
            if (rowText.StartsWith("version=") && rowText.Length > 8)
            {
                cxt.VersionRef = (PluginPackVersionRef)Enum.Parse(typeof(PluginPackVersionRef), rowText.Substring(8));
            }
            if (rowText.StartsWith("plainver=") && rowText.Length > 9)
            {
                cxt.PlainVersion = Version.Parse(rowText.Substring(9));
            }
            if (rowText.StartsWith("date=") && rowText.Length > 5)
            {
                DateTime releaseDate;
                if (DateTime.TryParseExact(rowText.Substring(5), "yyyy-MM-dd", null, DateTimeStyles.None, out releaseDate))
                {
                    cxt.ReleaseDate = releaseDate;
                }
            }
            if (rowText.StartsWith("platform=") && rowText.Length > 9)
            {
                cxt.PlatformCode = rowText.Substring(9);
            }
            if (rowText.StartsWith("api:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 4 && rowText.Length > firstIndexOfEqual + 1)
                {
                    var typeString = rowText.Substring(4, firstIndexOfEqual - 4);
                    var verString = rowText.Substring(firstIndexOfEqual + 1);

                    Version ver;
                    if (Version.TryParse(verString, out ver))
                    {
                        cxt.ApisUsed[typeString] = ver;
                    }
                    else
                    {
                        uint version = 0;
                        if (UInt32.TryParse(verString, out version))
                        {
                            cxt.ApisUsed[typeString] = new Version((int)version, 0, 0);
                        }
                    }
                }
            }
            if (rowText.StartsWith("type=") && rowText.Length > 5)
            {
                cxt.NativeTypes = rowText.Substring(5).Split(',');
            }
            if (rowText.StartsWith("depend=") && rowText.Length > 7)
            {
                cxt.Depends = rowText.Substring(7).Split(',');
            }
            if (rowText.StartsWith("component=") && rowText.Length > 10)
            {
                var comps = rowText.Substring(10).Split(',');
                foreach (var comp in comps)
                {
                    if (cxt.Components == null) cxt.Components = new Dictionary<string, ComponentInfo>();
                    if (!cxt.Components.ContainsKey(comp)) cxt.Components[comp] = new ComponentInfo();
                }
            }
            if (rowText.StartsWith("depend:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 7 && rowText.Length > firstIndexOfEqual + 1)
                {
                    var compString = rowText.Substring(7, firstIndexOfEqual - 7);
                    var relatedString = rowText.Substring(firstIndexOfEqual + 1);
                    if (cxt.Components == null) cxt.Components = new Dictionary<string, ComponentInfo>();
                    if (!cxt.Components.ContainsKey(compString)) cxt.Components[compString] = new ComponentInfo();
                    cxt.Components[compString].Depends = relatedString.Split(',');
                }
            }
            if (rowText.StartsWith("title:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 6 && rowText.Length > firstIndexOfEqual + 1)
                {
                    var targetComps = rowText.Substring(6, firstIndexOfEqual - 6).Split(':');
                    var title = rowText.Substring(firstIndexOfEqual + 1);
                    if (targetComps.Length == 2)
                    {
                        var langID = targetComps[0];
                        var compString = targetComps[1];

                        var language = Language.Invalid;
                        if (langID == "en") language = Language.English;
                        else if (langID == "zh" || langID == "ch") language = Language.Chinese;
                        if (language != Language.Invalid)
                        {
                            if (cxt.Components == null) cxt.Components = new Dictionary<string, ComponentInfo>();
                            if (!cxt.Components.ContainsKey(compString)) cxt.Components[compString] = new ComponentInfo();
                            if (cxt.Components[compString].Title == null) cxt.Components[compString].Title = new Dictionary<Language, string>();
                            cxt.Components[compString].Title[language] = title;
                        }
                    }
                }
            }
            if (rowText.StartsWith("app=") && rowText.Length > 4)
            {
                var appFiles = rowText.Substring(4).Split(',');
                if (appFiles.Length > 0) cxt.AppEntryFile = appFiles[0];
            }
            if (rowText.StartsWith("lib=") && rowText.Length > 4)
            {
                cxt.LibFiles = rowText.Substring(4).Split(',');
            }
            if (rowText.StartsWith("exe=") && rowText.Length > 4)
            {
                cxt.ExeFiles = rowText.Substring(4).Split(',');
            }
            if (rowText.StartsWith("driver=") && rowText.Length > 7)
            {
                cxt.DriverFileNames[""] = rowText.Substring(7).Split(',');
            }
            if (rowText.StartsWith("driver:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 7 && rowText.Length > firstIndexOfEqual + 1)
                {
                    cxt.DriverFileNames[rowText.Substring(7, firstIndexOfEqual - 7)] = rowText.Substring(firstIndexOfEqual + 1).Split(',');
                }
            }
            if (rowText.StartsWith("brief:"))
            {
                var firstIndexOfEqual = rowText.IndexOf('=');
                if (firstIndexOfEqual > 6 && rowText.Length > firstIndexOfEqual + 1)
                {
                    var langID = rowText.Substring(6, firstIndexOfEqual - 6);
                    Language language = Language.Invalid;
                    if (langID == "en") language = Language.English;
                    else if (langID == "zh" || langID == "ch") language = Language.Chinese;
                    if (language != Language.Invalid) cxt.Brief[language] = rowText.Substring(firstIndexOfEqual + 1);
                }
            }
            if (rowText.StartsWith("agency="))
            {
                cxt.FreeAgency = rowText.Substring(7) == "free";
            }
        }
    }
}
